package graph;

import graph.chart.GenericChart;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;

import javax.swing.JPanel;

public class GenericGraph extends JPanel implements MouseListener, MouseMotionListener{

	private static final long serialVersionUID = 1L;
	
	/* Default axises's settings */
	protected AxisArrangement xAxisArrangement = AxisArrangement.Horizontal;
	protected AxisArrangement yAxisArrangement = AxisArrangement.Vertical;
	protected AxisDirection xAxisDirection = AxisDirection.Normal;
	protected AxisDirection yAxisDirection = AxisDirection.Normal;
	protected AxisLocation xAxisLocation = AxisLocation.Center;
	protected AxisLocation yAxisLocation = AxisLocation.Center;
	
	protected Point screenCoordinateCenter = new Point(0, 0);
	
	protected double xScale = 0.01;
	protected double yScale = 0.01;
	
	protected Point mouseScreenCoordinates = new Point(0,0);
	
	/* graph border in pixels */
	protected int border = 10;
	/* xScale and yScale should be equal */
	protected boolean equalXYscales = true;
	/* automatic zoom to fit the chart data in graph canvas */
	protected boolean autoZoomToFit = true;
	protected boolean showMouseScreenCoordinates = false;
	protected boolean showMousePointCoordinates = false;
	
	
	protected ArrayList<GenericChart> charts = new ArrayList<GenericChart>();
	
	
	
	public GenericGraph(){
		setxAxisLocation(AxisLocation.Bottom);
		setyAxisLocation(AxisLocation.Center);
		
		/* Add mouse listeners */
		this.addMouseListener(this);
		this.addMouseMotionListener(this);
	}
	
	protected void paintGraph(Graphics2D g2D){
		recalculateScreenCoordinateCenter();
		
		if(autoZoomToFit){
			zoomToFit();
		}
		
		g2D.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
		g2D.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION, java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR);

		/* draw xAxis, yAxis */
		drawXAis(g2D);
		drawYAis(g2D);
		
		/* draw charts on canvas */
		for(GenericChart chart : charts){
			chart.paintChart(g2D);
		}
		
		if(this.showMouseScreenCoordinates){
			drawMouseScreenCoordinates(g2D);
		}
		if(this.showMousePointCoordinates){
			drawMousePointCoordinates(g2D);
		}
	}
	
	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		paintGraph((Graphics2D)g);
	}
	
	public void addChartToGraph(GenericChart chart){
		chart.setGenericGraph(this);
		charts.add(chart);
	}
	
	/***
	 * Calculates the xScale and yScale to zoom to fit the graph
	 * into the canvas.
	 */
	public void zoomToFit(){
		if(charts.size() > 0){
			PlotSet mainPlotSet = charts.get(0).getPlotSet();
			if(mainPlotSet != null){
				switch(xAxisArrangement){
					case Horizontal:{
						switch(yAxisLocation){
							//xScale = (mainPlotSet.getMaxXValue() - mainPlotSet.getMinXValue()) / (getWidth() - 2 * border);
							case Left:{
								xScale = (mainPlotSet.getMaxXValue()) / (getWidth() - 2 * border);
							};break;
							case Center:{
								xScale = (Math.max(Math.abs(mainPlotSet.getMaxXValue()), Math.abs(mainPlotSet.getMinXValue()))) / (getWidth()/2 - 2 * border);
							};break;
							case Right:{
								xScale = (mainPlotSet.getMinXValue()) / (getWidth() - 2 * border);
							};break;
						}
					};break;
					case Vertical:{
						switch(yAxisLocation){
							//xScale = (mainPlotSet.getMaxXValue() - mainPlotSet.getMinXValue()) / (getHeight() - 2 * border);
							case Bottom:{
								xScale = (mainPlotSet.getMaxXValue()) / (getHeight() - 2 * border);
							};break;
							case Center:{
								xScale = (Math.max(Math.abs(mainPlotSet.getMaxXValue()), Math.abs(mainPlotSet.getMinXValue()))) / (getHeight()/2 - 2 * border);
							};break;
							case Top:{
								xScale = (mainPlotSet.getMinXValue()) / (getHeight() - 2 * border);
							};break;
						}
					};break;
				}
				switch(yAxisArrangement){
					case Horizontal:{
						//yScale = (mainPlotSet.getMaxYValue() - mainPlotSet.getMinYValue()) / (getWidth() - 2 * border);
						switch(xAxisLocation){
							//xScale = (mainPlotSet.getMaxXValue() - mainPlotSet.getMinXValue()) / (getWidth() - 2 * border);
							case Left:{
								yScale = (mainPlotSet.getMaxYValue()) / (getWidth() - 2 * border);
							};break;
							case Center:{
								yScale = (Math.max(Math.abs(mainPlotSet.getMaxYValue()), Math.abs(mainPlotSet.getMinYValue()))) / (getWidth()/2 - 2 * border);
							};break;
							case Right:{
								yScale = (mainPlotSet.getMinYValue()) / (getWidth() - 2 * border);
							};break;
						}
					};break;
					case Vertical:{
						//yScale = (mainPlotSet.getMaxYValue() - mainPlotSet.getMinYValue()) / (getHeight() - 2 * border);
						switch(xAxisLocation){
							//xScale = (mainPlotSet.getMaxXValue() - mainPlotSet.getMinXValue()) / (getWidth() - 2 * border);
							case Bottom:{
								yScale = (mainPlotSet.getMaxYValue()) / (getHeight() - 2 * border);
							};break;
							case Center:{
								yScale = (Math.max(Math.abs(mainPlotSet.getMaxYValue()), Math.abs(mainPlotSet.getMinYValue()))) / (getHeight()/2 - 2 * border);
							};break;
							case Top:{
								yScale = (mainPlotSet.getMinYValue()) / (getHeight() - 2 * border);
							};break;
						}
					};break;
				}
			}
		}
		
		/* if both scales should be the same */
		if(this.equalXYscales){
			double scale = Math.max(xScale, yScale);
			xScale = scale;
			yScale = scale;
		}
		
		repaint();
	}
	
	protected void drawXAis(Graphics2D g2D){
		Point startPoint = null;
		Point endPoint = null;
		
		switch(xAxisArrangement){
			case Horizontal:{
				startPoint = new Point(border, screenCoordinateCenter.getY());
				endPoint = new Point(getWidth() - border, screenCoordinateCenter.getY());
				if(xAxisDirection == AxisDirection.Normal){
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() - 10, (int)endPoint.getY() - 5);
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() - 10, (int)endPoint.getY() + 5);
				}else{
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() + 10, (int)startPoint.getY() - 5);
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() + 10, (int)startPoint.getY() + 5);
				}
			}; break;
			case Vertical:{
				startPoint = new Point(screenCoordinateCenter.getX(), getHeight() - border);
				endPoint = new Point(screenCoordinateCenter.getX(), border);
				if(xAxisDirection == AxisDirection.Normal){
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() - 5, (int)endPoint.getY() + 10);
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() + 5, (int)endPoint.getY() + 10);
				}else{
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() - 5, (int)startPoint.getY() - 10);
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() - 5, (int)startPoint.getY() - 10);
				}
			}
		}

		g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)endPoint.getX(), (int)endPoint.getY());
	}
	
	protected void drawYAis(Graphics2D g2D){
		Point startPoint = null;
		Point endPoint = null;
		
		switch(yAxisArrangement){
			case Horizontal:{
				startPoint = new Point(border, screenCoordinateCenter.getY());
				endPoint = new Point(getWidth() - border, screenCoordinateCenter.getY());
				if(xAxisDirection == AxisDirection.Normal){
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() - 10, (int)endPoint.getY() - 5);
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() - 10, (int)endPoint.getY() + 5);
				}else{
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() + 10, (int)startPoint.getY() - 5);
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() + 10, (int)startPoint.getY() + 5);
				}
			}; break;
			case Vertical:{
				startPoint = new Point(screenCoordinateCenter.getX(), getHeight() - border);
				endPoint = new Point(screenCoordinateCenter.getX(), border);
				if(xAxisDirection == AxisDirection.Normal){
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() - 5, (int)endPoint.getY() + 10);
					g2D.drawLine((int)endPoint.getX(), (int)endPoint.getY(), (int)endPoint.getX() + 5, (int)endPoint.getY() + 10);
				}else{
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() - 5, (int)startPoint.getY() - 10);
					g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)startPoint.getX() - 5, (int)startPoint.getY() - 10);
				}
			}
		}
		
		g2D.drawLine((int)startPoint.getX(), (int)startPoint.getY(), (int)endPoint.getX(), (int)endPoint.getY());
	}
	
	public void drawMouseScreenCoordinates(Graphics g2D){
		String text = "(" + this.mouseScreenCoordinates.getX() + " , " + this.mouseScreenCoordinates.getY() + ")";
		g2D.drawString(text, 0, 10);
	}
	
	public void drawMousePointCoordinates(Graphics g2D){
		Point point = this.screenToPointCoordinates(mouseScreenCoordinates);
		
		String text = "(" + point.getX() + " , " + point.getY() + ")";
		g2D.drawString(text, 0, 20);
	}
	
	/***
	 * Transform the point coordinates to screen coordinates
	 * @param point
	 * @return
	 */
	public Point pointToScreenCoordinates(Point point){
		
		Point screenPoint = new Point(0, 0);
		Point _point = new Point(point.getX(), point.getY());
		
		if(this.xAxisDirection == AxisDirection.Reverse){
			_point.setX(-point.getX());
		}
		if(this.xAxisArrangement == AxisArrangement.Horizontal){
			screenPoint.setX(screenCoordinateCenter.getX() + _point.getX()/xScale);
		}
		if(this.xAxisArrangement == AxisArrangement.Vertical){
			screenPoint.setX(screenCoordinateCenter.getX() + _point.getY()/yScale);
		}
		
		if(this.yAxisDirection == AxisDirection.Reverse){
			_point.setY(-point.getY());
		}
		if(this.yAxisArrangement == AxisArrangement.Horizontal){
			screenPoint.setY(screenCoordinateCenter.getY() - point.getX()/xScale);
		}
		if(this.yAxisArrangement == AxisArrangement.Vertical){
			screenPoint.setY(screenCoordinateCenter.getY() - point.getY()/yScale);
		}
		
		return screenPoint;
	}
	
	/***
	 * Transform screen coordinates to point coordinates
	 * @param point
	 * @return
	 */
	public Point screenToPointCoordinates(Point screenPoint){
		
		Point point = new Point(0, 0);
		
		if(this.xAxisArrangement == AxisArrangement.Horizontal){
//			screenPoint.setX(screenCoordinateCenter.getX() + _point.getX()/xScale);
			point.setX((screenPoint.getX() - screenCoordinateCenter.getX()) * xScale);
		}
		if(this.xAxisArrangement == AxisArrangement.Vertical){
//			screenPoint.setX(screenCoordinateCenter.getX() + _point.getY()/yScale);
			point.setY((screenPoint.getX() - screenCoordinateCenter.getX()) * yScale);
		}
		if(this.xAxisDirection == AxisDirection.Reverse){
//			_point.setX(-point.getX());
			point.setX(-point.getX());
		}
		
		
		
		if(this.yAxisArrangement == AxisArrangement.Horizontal){
//			screenPoint.setY(screenCoordinateCenter.getY() - point.getX()/xScale);
			point.setX((screenCoordinateCenter.getY() - screenPoint.getY()) * xScale);
		}
		if(this.yAxisArrangement == AxisArrangement.Vertical){
//			screenPoint.setY(screenCoordinateCenter.getY() - point.getY()/yScale);
			point.setY((screenCoordinateCenter.getY() - screenPoint.getY()) * yScale);
		}
		if(this.yAxisDirection == AxisDirection.Reverse){
//			_point.setY(-point.getY());
			point.setX(-point.getY());
		}
		
		return point;
	}
	
	public AxisArrangement getxAxisArrangement() {
		return xAxisArrangement;
	}

	public void setxAxisArrangement(AxisArrangement xAxisArrangement) {
		this.xAxisArrangement = xAxisArrangement;
	}

	public AxisArrangement getyAxisArrangement() {
		return yAxisArrangement;
	}

	public void setyAxisArrangement(AxisArrangement yAxisArrangement) {
		this.yAxisArrangement = yAxisArrangement;
	}

	public AxisDirection getxAxisDirection() {
		return xAxisDirection;
	}

	public void setxAxisDirection(AxisDirection xAxisDirection) {
		this.xAxisDirection = xAxisDirection;
	}

	public AxisDirection getyAxisDirection() {
		return yAxisDirection;
	}

	public void setyAxisDirection(AxisDirection yAxisDirection) {
		this.yAxisDirection = yAxisDirection;
	}
	
	public void setxAxisLocation(AxisLocation axisLocation){
		this.xAxisLocation = axisLocation;
		recalculateScreenCoordinateCenter();
	}
	
	public void setyAxisLocation(AxisLocation axisLocation){
		this.yAxisLocation = axisLocation;
		recalculateScreenCoordinateCenter();
	}
	
	public boolean isShowMouseScreenCoordinates() {
		return showMouseScreenCoordinates;
	}

	public void setShowMouseScreenCoordinates(boolean showMouseScreenCoordinates) {
		this.showMouseScreenCoordinates = showMouseScreenCoordinates;
	}
	
	public boolean isShowMousePointCoordinates() {
		return showMousePointCoordinates;
	}

	public void setShowMousePointCoordinates(boolean showMousePointCoordinates) {
		this.showMousePointCoordinates = showMousePointCoordinates;
	}

	protected void recalculateScreenCoordinateCenter(){
		switch(xAxisArrangement){
			case Horizontal:{
				switch(xAxisLocation){
					case Bottom:{
						this.screenCoordinateCenter.setY(getHeight() - border);
					};break;
					case Center:{
						this.screenCoordinateCenter.setY(getHeight()/2);
					};break;
					case Top:{
						this.screenCoordinateCenter.setY(border);
					};break;
					default:{
						/* Left and Right are forbidden */
					};break;
				}
			}; break;
			case Vertical:{
				switch(xAxisLocation){
				case Left:{
					this.screenCoordinateCenter.setX(border);
				};break;
				case Center:{
					this.screenCoordinateCenter.setX(getWidth()/2);
				};break;
				case Right:{
					this.screenCoordinateCenter.setX(getWidth() - border);
				};break;
				default:{
					/* Bottom and Top are forbidden */
				};break;
			}
			};break;
		}
		
		switch(yAxisArrangement){
			case Horizontal:{
				switch(yAxisLocation){
					case Bottom:{
						this.screenCoordinateCenter.setY(getHeight() - border);
					};break;
					case Center:{
						this.screenCoordinateCenter.setY(getHeight()/2);
					};break;
					case Top:{
						this.screenCoordinateCenter.setY(border);
					};break;
					default:{
						/* Left and Right are forbidden */
					};break;
				}
			}; break;
			case Vertical:{
				switch(yAxisLocation){
					case Left:{
						this.screenCoordinateCenter.setX(border);
					};break;
					case Center:{
						this.screenCoordinateCenter.setX(getWidth()/2);
					};break;
					case Right:{
						this.screenCoordinateCenter.setX(getWidth() - border);
					};break;
					default:{
						/* Bottom and Top are forbidden */
					};break;
				}		
			};break;
		}
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mousePressed(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseDragged(MouseEvent arg0) {
		mouseScreenCoordinates.setX(arg0.getPoint().x);
		mouseScreenCoordinates.setY(arg0.getPoint().y);
		repaint();
	}

	@Override
	public void mouseMoved(MouseEvent arg0) {
		mouseScreenCoordinates.setX(arg0.getPoint().x);
		mouseScreenCoordinates.setY(arg0.getPoint().y);
		repaint();
	}

	
}
